package dynamic;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 动态代理
 * 静态代理只能代理某一种特定的类型
 * 动态代理可以代理任意类型，主要不知道代理的类型实现了什么方法。
 * 动态代理类是由jdk创建生成 asm
 *
 * jdk动态代理的执行流程
 * 【生成代理的一个过程】
 * 1.客户端调用Proxy.newProxyInstance
 * 2.通过asm生成了$Proxy0.class 然后通过classLoader加载到内存new了一个它的对象。
 * 3.实例化传入了InvocationHandler这个接口的实现对象。
 *
 * 【调用的过程】
 * 1.m 调用了play方法
 * 2.调用了$Proxy0.class的play方法 内部调用了 传入了InvocationHandler实例的invoke方法
 * 2.5 执行 method.invoke 之前操作 和 method.invoke 之后的操作。
 * 3.获取当前执行的方法在你传入的实例 执行实例上的你调用的方法。 method.invoke
 *
 *
 * ASM 到底是如何生成的 $Proxy0.class
 * 1.参数的检查
 * 2.getProxyConstructor
 * 3.ProxyCache Proxy的缓存池 每次生成代理如果代理生成过了就从池中拿。
 * 4.如果没有生成就通过 computeIfAbsent 创建
 * 5.通过ProxyBuild把它build出来。
 * 6.类的生成是通过 proxyClass = defineProxyClass(xxx)
 *   defineProxyClass
 *   1.生成接口的过程
 *   2.生成包的过程
 *   3.Generate the xxxx  真正的生成class
 *   4.System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");设置的参数会被用在
 *     这里
 *     ProxyGenerator.generateProxyClass
 *   5.generateProxyClass 是实际的落地class的内容，这里操纵的是字节码
 *     Files.write 落地
 *   6.generateClassFile 是真正的往class文件里面加内容了。
 *     addProxyMethod
 *     play是在
 *     methods.add 添加的。
 *   7.methods.add 点进去 看到一个接口，去找实现类 选中add 右键 go to implexxxAtions
 *     找到xxx.asm.xxx点过去。 这个类是没有源代码的是idea的反编译的。
 *     用的是objectweb.asm 动态代理搞懂需要能明白asm
 *   8.asm是直接操纵的二进制数据。 直接改内存里面的二进制数据的。
 *   9.asm主要是操纵二进制字节码的信息的。非常小 慎用。 用了asm后因为不需要编译才可以被称为动态语言。直接撸内存了还编译个鬼。
 *   10.动态语言就是可以运行过程中可以动态的对对象进行方法和属性的添加。
 *   11.反射只能找出类的属性方法不能修改。
 *   12. asm 如果想操纵二进制要先搞懂二进制的内容
 *       二进制 操纵二进制是对class文件里面的数进行修改。
 *       需要搞懂class类文件的结构内容
 *   13.任何语言只要能编译成class【遵守class规范】就可以在jvm上执行。
 *   14.jdk动态代理 反射生成的代理必须面向接口
 *     也就说Tank被代理的类是要实现接口的。 如果没有实现接口是不能用动态代理的。 new Class[]{接口名字.class} 要实现这个的
 *     只有知道了这个接口才知道如何生成代理类实现哪些接口。
 *
 * 7.构造函数的生成是通过 proxyClass.getConstructor()
 */
interface Movable{
    void play();
}

class Tank implements Movable{

    @Override
    public void play() {
        System.out.println("tank play");
    }
}

public class Dynamic {
    public static void main(String[] args) {
        System.out.println("dynamic");
        System.out.println(Movable.class);

        // 动态代理类的配置落入本地 idea 会把反编译的类落到磁盘上来。
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

        // newProxyInstance 返回一个代理动态生成的代理 这个代理类是动态生成的。 通过自动生成的代理类，在返回实例。
        // Tank.class.getClassLoader 使用 Tank的classLoader 把将来生成的代理类 load到内存。和被代理对象用一个行了。
        // new Class 你 new 出来的 代理类要实现哪些接口
        // InvocationHandler 这个是实际代理要做的事情，自定义类去实现这个接口。这里是匿名类实现了InvocationHandler接口
        Movable m = (Movable) Proxy.newProxyInstance(Tank.class.getClassLoader(), new Class[]{
              Movable.class
      }, new InvocationHandler() {
            /**
             *
             * @param proxy 这个是 $Proxy0.class 这个代理类的实例。直白说就是 Movable m 的 m
             * @param method 哪个方法被调用的时候。 this.getClass().getMethods() 获取类有多少方法。【反射】
             *               method.getName() 获取当前调用的是哪个方法。 m.play 这里的 getName 是 play
             * @param args m.play(args) 这里传的参数 会透传到invoke 的Object[] args上
             *             被代理对象的方法是在method.invoke上调用的
             *             当前这个method 是 play这个方法 这个有点js的 call的意思 第一个参数引用，第二个参数
             *             也就是说play运行在tank上并且传入args的参会通俗将就是执行tank的play
             *             直白说传了什么引用就在该引用上执行play方法。
             *             这里传的Tank的引用所以就执行了Tank的play
             *
             *             public Object invoke
             *             如果 接口 Movable play 定义的返回值是void 这里返回的是null
             *             public Object invoke
             *             返回值用的是 m.xxx() 方法的返回值。
             * @return
             * @throws Throwable
             */
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              System.out.println("dynamic proxy");
              return method.invoke(new Tank(), args);
          }
      });


        // 这里调用了 play方法是如何调用到了 InvocationHandler 实例的invoke方法。
        /**
         * 1. new $Proxy0.class 中间的代理类先传 InvocationHandler 的实现类。
         * 2. $Proxy0 class 构造方法调用 super proxy 传入 InvocationHandler 的实现类  Proxy父类拿着InvocationHandler它的实例。
         * 3. 我们可以看到 Movable.class 接口的实现方法被asm 写入到了 $Proxy0这个class类中去了（写入了 implements 和实现play）。
         * 因为它实现了 Movable接口。
         * 一开始 m.play()先调用 $Proxy0 这个的play方法。
         * 4. super.h.invoke 这个是调用父类Proxy的 InvocationHandler 的 invoke 方法
         * 5. 这里就直接接到了 new InvocationHandler() 上面来 调用invoke
         */
        m.play();
    }
}
